//1.输入一个值，返回其数据类型
function returnDataType(param){
    return Object.prototype.toString.call(param);
}
// console.log(typeof returnDataType({}));//string
// console.log(returnDataType({}));
// console.log(returnDataType([]));
// console.log(returnDataType(null))
// console.log(returnDataType(undefined))
// console.log(returnDataType(function(){}))
// [object Object]
// [object Array]
// [object Null]
// [object Undefined]
// [object Function]

//2.数组去重
function unique1(arr){
    if(!Array.isArray(arr)){
        throw new Error(`${arr} is Non array type`);
    }
    return [...new Set(arr)];
}
function unique2(arr){
    if(!Array.isArray(arr)){
        throw new Error(`${arr} is Non array type`);
    }
    return Array.from(new Set(arr))
}
function unique3(arr){
    if(!Array.isArray(arr)){
        throw new Error(`${arr} is Non array type`);
    }
    let obj = {};
    return arr.filter(ele=>{
        if(!obj[ele]){
            obj[ele] = true;
            return true;
        }
    })
}
function unique4(arr){
    if(!Array.isArray(arr)){
        throw new Error(`${arr} is Non array type`);
    }
    let newArr = [];
    arr.forEach(ele=>{
        if(newArr.indexOf(ele) === -1){
            newArr.push(ele);
        }
    })
    return newArr;
}
// let numArr = [1,1,2,4,5,5,4,3,3];
// let strArr = ['aa','ab','ac','bc','ab','bc','aa'];
// console.log(unique1(numArr));
// console.log(unique1(strArr));
// console.log(unique2(numArr));
// console.log(unique2(strArr));
// console.log(unique3(numArr));
// console.log(unique3(strArr));
// console.log(unique4(numArr));
// console.log(unique4(strArr));
// [ 1, 2, 4, 5, 3 ]
// [ 'aa', 'ab', 'ac', 'bc' ]
// [ 1, 2, 4, 5, 3 ]
// [ 'aa', 'ab', 'ac', 'bc' ]
// [ 1, 2, 4, 5, 3 ]
// [ 'aa', 'ab', 'ac', 'bc' ]
// [ 1, 2, 4, 5, 3 ]
// console.log(unique1('ss'));
// Error: ss is Non array type

//3.字符串去重
String.prototype.unique = function(){
    let obj = {};
    let newStr = '';
    let len = this.length;
    for(let i = 0; i< len;i++){
        if(!obj[this[i]]){
            newStr += this[i];
            obj[this[i]] = true;
        }
    }
    return newStr;
}
// console.log('abcdabcdefghkk'.unique())
// abcdefghk
// 4.去除连续的字符串
function strUnique(str){
    return str.replace(/(\w)\1+/g,'$1');
}
// console.log(strUnique('aabbccfgfac'));
// abcfgfac

//5.深拷贝，浅拷贝
//深克隆(深克隆不考虑函数);
function deepClone(sourceObject,targetObject = {}){
    //1.对 sourceObject 原对象遍历所有的属性
    for(let key in sourceObject){
        //2.判断该属性是否为对象自身属性而非对象原型上的属性
        if(sourceObject.hasOwnProperty(key)){
            //3 判断是否是引用类型
            if(typeof sourceObject[key] === 'object' ||typeof sourceObject[key] === 'function'){
                //3.1 判断是否是null;
                if(Object.prototype.toString.call(sourceObject[key]) === '[object Null]'){
                    targetObject[key] = JSON.parse(JSON.stringify(sourceObject[key]));
                }
                //3.2 判断是否是Array
                if(Object.prototype.toString.call(sourceObject[key]) === '[object Array]'){
                    targetObject[key] = [];
                    deepClone(sourceObject[key],targetObject[key]);
                }
                //3.3 判断是否是Function
                // undefined、任意的函数以及 symbol 值，在序列化过程中会被忽略（出现在非数组对象的属性值中时）
                // 或者被转换成 null（出现在数组中时）。函数、undefined 被单独转换时，会返回 undefined，
                if(Object.prototype.toString.call(sourceObject[key]) === '[object Function]'){
                    console.log(sourceObject[key])
                    // console.log(JSON.stringify(sourceObject[key]));
                    // targetObject[key] = JSON.parse(JSON.stringify(sourceObject[key]));
                    targetObject[key] = sourceObject[key];
                }
                //3.4 判断是否是Object
                if(Object.prototype.toString.call(sourceObject[key]) === '[object Object]'){
                    targetObject[key] = {};
                    deepClone(sourceObject[key],targetObject[key])
                }
            //4.判断是否是基本类型
            } else {
                targetObject[key] = sourceObject[key];
            }
        }
    }
    return targetObject;
}
//深浅克隆是针对引用值
function deepClone1(sourceObject){
    //1.判断原对象类型是否为非对象，如果是，则直接返回原对象
    if(typeof (sourceObject) !=='object'){
        return sourceObject;
    }
    let targetObject;
    if(Object.prototype.toString.call(sourceObject) === '[object Array]'){
        //数组
        targetObject = [];
    } else {
        //对象
        targetObject = {};
    }
    for(let key in sourceObject){
        if(sourceObject.hasOwnProperty(key)){
            targetObject[key] = deepClone1(sourceObject[key]);
        }
    }
    return targetObject;
}
// 无法复制函数
// let object = JSON.parse(JSON.stringify(sourceObject))
// let sourceObject = {
//     name:'zw',
//     age:25,
//     sex:true,
//     showName:function(){return 'hello world'},
//     hobby:['aa','bb','cc'],
//     personInfo:{
//         score:45
//     },
//     foo:null,
// }
// let targetObject = deepClone(sourceObject,{});
// console.log(targetObject);
// console.log(targetObject.foo === sourceObject.foo);//true null === null ==>true
// console.log(targetObject.showName === sourceObject.showName);//true
// console.log(targetObject.personInfo === sourceObject.personInfo);//false
// console.log(targetObject.hobby === sourceObject.hobby);//false
// console.log('------------------------------------------------------------------')
// let targetObject1 = deepClone1(sourceObject);
// console.log(targetObject1);
// console.log(targetObject1.foo === sourceObject.foo);//false
// console.log(targetObject1.showName === sourceObject.showName);//true
// console.log(targetObject1.personInfo === sourceObject.personInfo);//false
// console.log(targetObject1.hobby === sourceObject.hobby);//false


//6.reverse 底层原理和扩展
// 数组首尾元素变换
Array.prototype.reversePosition = function(){
    let len = this.length;
    for(let i = 0; i < Math.ceil(len / 2);i++){
        let temp = this[i];
        this[i]  = this[len - i -1];
        this[len - i - 1] = temp;
    }
    return this;
}
// let arr = [1,2,7,9,12];
// console.log(arr.reversePosition());
// [ 12, 9, 7, 2, 1 ]

//7.圣杯模式的继承
function grailModeInherit(Target,Origin){
    //1.创建构造函数
    function F(){}
    //2.将构造函数的原型指向原构造函数的原型
    F.prototype = Origin.prototype;
    //3.将新构造函数的原型指向新创建构造函数的实例
    Target.prototype = new F();
    //4.将新构造函数的原型上的构造函数属性指向新构造函数
    Target.prototype.constructor = Target;
    //5.将找到Target的超级父类，知道Target最终继承了谁
    Target.prototype.uber = Origin.prototype;
}
// function Father(){}
// function Son(){}
// Father.prototype.desc = 'Hello world';
// grailModeInherit(Son,Father);
// let father = new Father();
// let son = new Son();
// console.log(Son.prototype.__proto__.desc);//Hello world
// console.log(son.desc);//Hello world

//8.找出字符串中第一次只出现一次的字母
String.prototype.firstAppear = function(){
    let obj = {};
    let len = this.length;
    for(let i = 0;i < len;i++){
        if(obj[this[i]]){
            obj[this[i]] +=1;
        }else{
            obj[this[i]] = 1;
        }
    }
    let charArr = [];
    for(let key in obj){
        if(obj[key] === 1){
            charArr.push(key);
        }
    }
    return charArr;
}
// console.log('aabbccdefgfe'.firstAppear());
// [ 'd', 'g' ]

//9.找出元素的第n级父元素
function findParentEle(ele,n){
    while(ele && n){
        //parentNode:父节点，parentElement：父元素节点
        ele = ele.parentElement ? ele.parentElement : ele.parentNode;
        n--;
    }
    return ele;
}
//10.返回元素的第n个兄弟节点
function findSiblingElement(ele,n){
    while(ele && n){
        if(n>0){
            //nextElementSibling:返回当前元素在其父元素的子元素节点中的后一个元素节点,如果该元素已经是最后一个元素节点,则返回null,该属性是只读的.
            if(ele.nextElementSibling){
                ele = ele.nextElementSibling;
            }else{
                //返回其父节点的 childNodes 列表中紧跟在其后面的节点，如果指定的节点为最后一个节点，则返回 null。
                for(ele = ele.nextSibling; ele && ele.nodeType !== 1;ele = ele.nextSibling){}
            }
            n--;
        } else {
            //previousElementSibling 返回当前元素在其父元素的子元素节点中的前一个元素节点,如果该元素已经是第一个元素节点,则返回null,该属性是只读的.
            if(ele.previousElementSibling){
                ele = ele.previousElementSibling;
            } else {
                for(ele = ele.previousElementSibling; ele && ele.nodeType !== 1; ele=ele.previousElementSibling){
                }
            }
            n++;
        }
    }
    return ele;
}

//11.封装 myChildren 解决浏览器兼容性问题
function myChildren(ele){
    let children = ele.childNodes;
    let arr = [];
    let len = children.length;
    for(let i = 0;i<len;i++){
        // 判断是否是元素节点
        if(children[i].nodeType === 1){
            arr.push(children[i]);
        }
    }
    return arr;
}

//12.判断元素有没有子元素
function hasChildElement(ele){
    //方法一
    // let childrenList = ele.children;
    // return childrenList.length > 0;
    //方法二
    // let nodeList = ele.childNodes;
    // return Array.from(nodeList).some(item=>{
    //     return item.nodeType === 1;
    // })
}
//13.一个元素插入另一个元素的后面
// Element.prototype.insertAfter = function(target,ele){
    //1.获取参照节点的下一个同胞节点。
    // let nextEle = ele.nextElementSibling;
    //----------------------------------
    //2.判断参照节点是否是该父节点的最后一个子元素节点。
    // if(nextEle === null){
    //     //3.参照节点为末尾子节点。
    //     this.appendChild(target);
    // } else {
    //     //4.参照节点存在下一个兄弟节点，则将其作为 insertBefore 的参照节点。
    //     this.insertBefore(target,nextEle);
    // }
    //-------------------------------
    //3. 2部分的代码可以直接采用如下代码替换
    // this.insertBefore(target,nextEle);
    //}

//14.获取当前的时间（年月日时分秒）
function getDateTime(){
    let date = new Date();
    let year = date.getFullYear(),
        moth = format(date.getMonth() + 1),
        day  = format(date.getDate()),
        hour = format(date.getHours()),
        minute = format(date.getMinutes()),
        second = format(date.getSeconds());
    function format(value){
        let strValue = value.toString();
        return strValue.padStart(2,'0');
    }
    return `${year}年${moth}月${day}日 ${hour}时${minute}分${second}秒`
}
// console.log(getDateTime())

//15.获得滚动条的滚动距离
function getScrollOffset(){
    //pageXOffset 和 pageYOffset 属性返回文档在窗口左上角水平和垂直方向滚动的像素。
    //IE 8 及 更早 IE 版本不支持该属性,但可以使用 document.documentElement.scrollLeft 和 document.documentElement.scrollTop 属性 。
    if(window.pageXOffset){
        return {
            x: window.pageXOffset,
            y: window.pageYOffset
        }
    } else {
        return {
            x: document.body.scrollLeft + document.documentElement.scrollLeft,
            y: document.body.scrollTop + document.documentElement.scrollTop
        }
    }
}

//16.获得视口尺寸
function getViewPortOffset(){
    if(window.innerWidth){
        return {
            w: window.innerWidth,
            h: window.innerHeight
        }
    } else {
        // IE8 及其以下
        if(document.compatMode === 'BackCompat'){
            //如果为怪异模式
            return {
                w: document.body.clientWidth,
                h: document.body.clientHeight
            }
        } else {
            //标准模式
            return {
                w: document.documentElement.clientWidth,
                h: document.documentElement.clientHeight
            }
        }
    }
}

//17.获取任意元素的任意属性
function getAnyAttribute(ele,prop){
    //Window.getComputedStyle()方法返回一个对象，该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。
    //私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。
    //非 IE 浏览器
    // DOM 使用 getComputedStyle() 方法获取目标对象的显示样式，但是它属于 document.defaultView 对象。
    // getComputedStyle() 方法包含两个参数：第 1 个参数表示元素，用来获取样式的对象；第 2 个参数表示伪类字符串，定义显示位置，一般可以
    // 省略或者设置为 null。
    //IE 浏览器
    // IE 使用 currentStyle 对象读取元素的最终显示样式，是只读对象。currentStyle 对象包含元素的 style 属性，以及浏览器预定义的默认
    // style 属性。
    return window.getComputedStyle ? window.getComputedStyle(ele,null)[prop]:ele.currentStyle[prop];
}
//18.绑定事件的兼容代码
function bindingEvent(ele,type,handle){
    //1.非IE和非IE9
    if(ele.addEventListener){
        ele.addEventListener(type,handle,false);
        //2.IE兼容
    }else if(ele.attachEvent){
        ele.attachEvent('on'+type,function(){
            handle.call(ele);
        })
    }else{
        //3.通用绑定事件
        ele['on'+type] = handle;
    }
}

//19.解绑事件的兼容代码
function removeEvent(ele,type,handle){
    //1.非IE和非IE9
    if(ele.removeEventListener){
        ele.removeEventListener(type,handle,false);
        //2.IE6~IE8
    }else if(ele.detachEvent){
        ele.detachEvent('on'+type,handle)
    }else{
        //3.通用绑定事件
        ele['on'+type] = null;
    }
}
//20.取消冒泡的兼容代码
function stopBubble(e){
    if(e && e.stopPropagation){
        //阻止捕获和冒泡阶段中当前事件的进一步传播。但是，它不能防止任何默认行为的发生； 例如，对链接的点击仍会被处理。
        e.stopPropagation();
    } else {
        window.event.cancelBubble = true;
    }
}
//21.阻止默认事件兼容性写法
// 1. w3C标准的阻止默认行为的方法是 preventDefualt()
// 2. IE中阻止事件的默认行为的属性是 returnValue，为：true不阻止，false：阻止事件的默认行为
function stopDefaultEvent(event){
    event = event || window.event;
    if(event.preventDefault){
        event.preventDefault();
    }else{
        event.returnValue = false;
    }
}

